﻿
Imports System.Security

<CLSCompliant(True)> _
Partial Public Class TipsDataSet

#Region " Constructors "

    ''' <summary>
    ''' Creates a new instance of this dataset and loads the instance with the data in the xml file specified in the parameter.
    ''' </summary>
    ''' <param name="xmlFileName">System.String - the XML file to load.</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal xmlFileName As String)
        MyClass.New()

        If String.IsNullOrEmpty(xmlFileName) Then _
            Throw New ArgumentNullException("xmlFileName")

        If Not My.Computer.FileSystem.FileExists(xmlFileName) Then
            Throw New ArgumentException(String.Format("{0} does not exist.", xmlFileName), "xmlFileName")

        Else

            Try
                'Suppress the Tips ColumnChangingEvent during the load process.
                Me.Tips.SuppressColumnChangingEvent()
                Me.ReadXml(xmlFileName, XmlReadMode.IgnoreSchema)
                Me.Tips.ActivateColumnChangingEvent()
            Catch ex As SecurityException
                Throw New SecurityException(String.Format("'{0}' does not have read permissions.", xmlFileName), ex)
            End Try

        End If
    End Sub

#End Region

#Region " Methods "

    ''' <summary>
    ''' Saves the content of this instance to the specified XML file.
    ''' </summary>
    ''' <param name="xmlFileName">System.String - the XML file to write to.</param>
    ''' <remarks>This method will also write the XML schema.</remarks>
    Public Sub SaveToDisc(ByVal xmlFileName As String)
        If String.IsNullOrEmpty(xmlFileName) Then _
            Throw New ArgumentNullException("xmlFileName")

        Try
            Me.AcceptChanges()
            Me.WriteXml(xmlFileName, XmlWriteMode.WriteSchema)
        Catch ex As SecurityException
            Throw New SecurityException(String.Format("'{0}' does not have write permissions.", xmlFileName), ex)
        End Try
    End Sub

#End Region

#Region " Nested Types "

    Partial Class TipsDataTable

#Region " Fields "

        Private _ColumnChangingEventSuppressed As Boolean = False

#End Region

#Region " Properties "

        ''' <summary>
        ''' Gets a boolean indicating if the ColumnChanging event is currently suppressed.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Friend ReadOnly Property ColumnChangingEventSuppressed() As Boolean
            Get
                Return _ColumnChangingEventSuppressed
            End Get
        End Property

#End Region

#Region " Methods "

        ''' <summary>
        ''' Suppresses the ColumnChanging event.
        ''' </summary>
        ''' <remarks>Intended for use when certain operations within the ColumnChanging event would undesirably trigger the ColumnChanging event.</remarks>
        Friend Sub SuppressColumnChangingEvent()
            _ColumnChangingEventSuppressed = True
        End Sub

        ''' <summary>
        ''' Re-Enables the ColumnChanging event.
        ''' </summary>
        ''' <remarks></remarks>
        Friend Sub ActivateColumnChangingEvent()
            _ColumnChangingEventSuppressed = False
        End Sub

        ''' <summary>
        ''' Marks the tipIDs in the given array of integers as having been displayed
        ''' </summary>
        ''' <param name="tipIDsToMark">Integer() - an array of Integers which specifies which TipIDs to mark.</param>
        ''' <remarks></remarks>
        Public Sub MarkTipsAsDisplayed(ByVal tipIDsToMark As Integer())
            If tipIDsToMark Is Nothing Then _
                Throw New ArgumentNullException("tipIDsToMark")

            For Each tipID As Integer In tipIDsToMark
                Dim tipRow As TipsRow = Me.FindByTipID(tipID)
                If tipRow Is Nothing Then
                    Throw New ArgumentException("Parameter contains at least one invalid TipID.  All TipIDs must point to a valid row in the Tips table.", _
                                                "tipIDsToMark")
                Else
                    If tipRow.IsFirstDisplayedDateNull() Then
                        tipRow.FirstDisplayedDate = Now
                    End If
                    tipRow.LastDisplayedDate = Now
                End If
            Next
        End Sub

        ''' <summary>
        ''' Sets each not deleted or detached row in this instance to have null displayed dates.
        ''' </summary>
        ''' <returns>Boolean - indicating success.</returns>
        ''' <remarks>This is useful for resetting the display dates to ensure tips that have already been displayed will display again.</remarks>
        Public Function ResetDisplayedDates() As Boolean
            For Each tipRow As TipsRow In Me
                If tipRow.RowState <> DataRowState.Detached AndAlso tipRow.RowState <> DataRowState.Deleted Then
                    tipRow.SetFirstDisplayedDateNull()
                    tipRow.SetLastDisplayedDateNull()
                End If
            Next
            Return True
        End Function

        ''' <summary>
        ''' Sets each not deleted or detached row in this instance to have null last displayed dates.
        ''' </summary>
        ''' <returns>Boolean - indicating success.</returns>
        Public Function ResetLastDisplayedDates() As Boolean
            For Each tipRow As TipsRow In Me
                If tipRow.RowState <> DataRowState.Detached AndAlso tipRow.RowState <> DataRowState.Deleted Then
                    tipRow.SetLastDisplayedDateNull()
                End If
            Next
            Return True
        End Function

        ''' <summary>
        ''' Returns true if all tips have been displayed since the last time the "last" displayed dates were re-set.
        ''' </summary>
        ''' <returns>Boolean - True if the LastDisplayedDate on all tips is not null; False otherwise.</returns>
        ''' <remarks></remarks>
        Public Function AllTipsDisplayed() As Boolean
            For Each tipRow As TipsRow In Me
                If Not tipRow.RowState.Equals(DataRowState.Deleted) AndAlso Not tipRow.RowState.Equals(DataRowState.Detached) Then
                    If tipRow.IsLastDisplayedDateNull() Then
                        Return False
                    End If
                End If
            Next
            Return True
        End Function

        ''' <summary>
        ''' Adjusts the display order of tiprows between the specified current and new display order up or down 1 and appropriate.
        ''' </summary>
        ''' <param name="currentDisplayOrder">Integer - current display order of the row that is changing.</param>
        ''' <param name="newDisplayOrder">Integer - new display order of the row that is changing.</param>
        ''' <returns>Boolean - indicating success.</returns>
        ''' <remarks>This method is indended when the display order of a tip changes to adjust the other rows to keep the display order proper.</remarks>
        Private Function AdjustDisplayOrders(ByVal currentDisplayOrder As Integer, ByVal newDisplayOrder As Integer) As Boolean
            If currentDisplayOrder = newDisplayOrder Then
                Return True
            Else

                For Each tip As TipsRow In Me

                    If tip.RowState <> DataRowState.Deleted AndAlso tip.RowState <> DataRowState.Detached Then

                        If newDisplayOrder > currentDisplayOrder Then
                            If tip.DisplayOrder > currentDisplayOrder And _
                                   tip.DisplayOrder <= newDisplayOrder Then
                                tip.DisplayOrder -= 1
                            End If

                        ElseIf newDisplayOrder < currentDisplayOrder Then
                            If tip.DisplayOrder >= newDisplayOrder And _
                                   tip.DisplayOrder < currentDisplayOrder Then
                                tip.DisplayOrder += 1
                            End If

                        End If

                    End If 'row not deleted or detached

                Next tip

                Return True
            End If 'current and new equal

        End Function

        ''' <summary>
        ''' Returns the given display order unless it is below the minimum or above the maximum in which case it return the min or max as approporate.
        ''' </summary>
        ''' <param name="proposedDisplayOrder">Integer - the display order to check.</param>
        ''' <returns>Integer - adjusted display order</returns>
        ''' <remarks></remarks>
        Private Function GetAdjustedNewDisplayOrder(ByVal proposedDisplayOrder As Integer) As Integer
            If proposedDisplayOrder < 1 Then _
                Return 1

            If proposedDisplayOrder > Me.Rows.Count Then _
                Return Me.Count

            Return proposedDisplayOrder
        End Function

        ''' <summary>
        ''' Adjusts the other DisplayOrders as if the row with the given display order were removed.
        ''' </summary>
        ''' <remarks></remarks>
        Friend Sub RemoveDisplayOrder(ByVal displayOrderToRemove As Integer)

            For Each tip As TipsRow In Me
                If tip.RowState <> DataRowState.Deleted AndAlso tip.RowState <> DataRowState.Detached Then

                    If tip.DisplayOrder > displayOrderToRemove Then
                        tip.DisplayOrder -= 1
                    End If

                End If
            Next

        End Sub

        ''' <summary>
        ''' Sets the modified date of the given row if the specified current and proposed values are not equal.
        ''' </summary>
        ''' <param name="tipRow">TipRow - the row for which to modify.</param>
        ''' <param name="currentValue">String - the current value to use in the comparison.</param>
        ''' <param name="proposedValue">String - the proposed value to use in the comparison.</param>
        ''' <remarks></remarks>
        Private Sub UpdateDateModified(ByVal tipRow As TipsRow, ByVal currentValue As String, ByVal proposedValue As String)

            If Not tipRow.RowState.Equals(DataRowState.Deleted) Then
                If Not currentValue.Equals(proposedValue) Then
                    SuppressColumnChangingEvent()
                    tipRow.DateModified = Now
                    ActivateColumnChangingEvent()

                End If
            End If

        End Sub

#End Region

#Region " Event Handlers "

        Private Sub TipsDataTable_ColumnChanging(ByVal sender As Object, ByVal e As System.Data.DataColumnChangeEventArgs) Handles Me.ColumnChanging
            If Not ColumnChangingEventSuppressed() Then

                If e.Column.ColumnName = "TipCaption" Then

                    Dim thisTipRow As TipsRow = CType(e.Row, TipsRow)
                    If Not thisTipRow.RowState.Equals(DataRowState.Deleted) Then
                        UpdateDateModified(thisTipRow, thisTipRow.TipCaption, e.ProposedValue.ToString())
                    End If

                ElseIf e.Column.ColumnName = "TipContent" Then

                    Dim thisTipRow As TipsRow = CType(e.Row, TipsRow)
                    If Not thisTipRow.RowState.Equals(DataRowState.Deleted) Then
                        UpdateDateModified(thisTipRow, thisTipRow.TipContent, e.ProposedValue.ToString())
                        thisTipRow.TipContentPlainText = StringUtilities.ConvertRtfToPlainText(thisTipRow.TipContent)
                    End If

                ElseIf e.Column.ColumnName = "DisplayOrder" Then

                    Dim thisTipRow As TipsRow = CType(e.Row, TipsRow)
                    'Get the adjusted display order which corrects the proposed order to be within limits (if applicable).
                    Dim adjustedDisplayOrder As Integer = GetAdjustedNewDisplayOrder(Convert.ToInt32(e.ProposedValue))
                    SuppressColumnChangingEvent()
                    'Set the proposed display order.
                    e.ProposedValue = adjustedDisplayOrder
                    'Adjust the display orders in other rows by 1 to compensate for this change.
                    AdjustDisplayOrders(thisTipRow.DisplayOrder, adjustedDisplayOrder)
                    ActivateColumnChangingEvent()

                End If

            End If
        End Sub

        Private Sub TipsDataTable_RowDeleting(ByVal sender As Object, ByVal e As System.Data.DataRowChangeEventArgs) Handles Me.RowDeleting
            Dim theDeletedRow As TipsRow = CType(e.Row, TipsRow)
            SuppressColumnChangingEvent()
            'Adjust the DisplayOrder of the other rows in the table to compensate for this row's absence
            If Not theDeletedRow.IsDisplayOrderNull() Then
                RemoveDisplayOrder(theDeletedRow.DisplayOrder)
            End If
            theDeletedRow.SetDisplayOrderNull()
            ActivateColumnChangingEvent()
        End Sub

        Private Sub TipsDataTable_TableNewRow(ByVal sender As Object, ByVal e As System.Data.DataTableNewRowEventArgs) Handles Me.TableNewRow
            Dim theNewRow As TipsRow = CType(e.Row, TipsRow)
            SuppressColumnChangingEvent()
            theNewRow.DateCreated = DateTime.Now
            theNewRow.DisplayOrder = Me.Rows.Count + 1
            ActivateColumnChangingEvent()
        End Sub

#End Region

    End Class

#End Region

End Class
